{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Risk-Parity Portfolio Analysis\n",
    "In this notebook, we will analyze the risk-parity portfolio by downloading financial data, calculating returns, computing risk-parity weights, and evaluating portfolio performance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import yfinance as yf\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.dates as mdates\n",
    "import matplotlib.ticker as ticker"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 1: Download Financial Data\n",
    "We will download front-month futures data for S&P500, 10-year Treasuries, gold, and US dollar using the `yfinance` library."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Download front-month futures data\n",
    "symbols = ['ES=F', 'ZN=F', 'GC=F', 'DX=F']\n",
    "data = yf.download(symbols)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 2: Resample Data\n",
    "To reduce noise, we will resample the data to monthly frequency."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Resample data to monthly frequency\n",
    "data = data.resample('M').last()\n",
    "data.index = pd.to_datetime(data.index)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 3: Clean and Prepare Data\n",
    "We will subset adjusted close prices, fill NaNs with the value known at time `t`, and drop rows with unknown prices."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Subset adjusted close prices and fill NaNs\n",
    "prices = data['Adj Close'].ffill().dropna()\n",
    "prices.index = pd.to_datetime(prices.index)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 4: Calculate Returns\n",
    "We will calculate the returns of the prices."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Compute logarithmic returns\n",
    "returns = prices.pct_change().fillna(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 5: Compute Risk-Parity Weights\n",
    "We will compute the risk-parity weights using a 36-month rolling window."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_risk_parity_weights(returns, window_size=36):\n",
    "    # Compute volatility known at time t\n",
    "    rolling_vol = returns.rolling(window_size).std()\n",
    "    rolling_inverse_vol = 1 / rolling_vol\n",
    "    # Divide inverse volatility by the sum of inverse volatilities\n",
    "    risk_parity_weights = rolling_inverse_vol.apply(\n",
    "        lambda column: column / rolling_inverse_vol.sum(1)\n",
    "    )\n",
    "    # Shift weights by one period to use only information available at time t\n",
    "    return risk_parity_weights.shift(1)\n",
    "\n",
    "risk_parity_weights = compute_risk_parity_weights(returns, 36)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 6: Calculate Weighted Returns\n",
    "We will calculate the weighted returns using the risk-parity weights."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Calculate weighted returns\n",
    "weighted_returns = (returns * risk_parity_weights).dropna()\n",
    "risk_parity_portfolio_returns = weighted_returns.sum(axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 7: Evaluate Portfolio Performance\n",
    "We will evaluate the performance of the risk-parity portfolio by calculating various metrics such as annualized mean return, annualized volatility, skewness, kurtosis, maximum drawdown, Sharpe ratio, Sortino ratio, and Calmar ratio."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate_performance(returns, freq=12):\n",
    "    annualized_mean_return = (returns.mean() * freq)\n",
    "    print()\n",
    "    print(f\"annualized_mean_return: {np.round(annualized_mean_return * 100, 2)}%\")\n",
    "    annualized_volatility = (returns.std() * np.sqrt(freq))\n",
    "    print(f\"annualized_volatility: {np.round(annualized_volatility * 100, 2)}%\")\n",
    "    skewness = (returns.skew())\n",
    "    print(f\"skewness: {np.round(skewness, 1)}\")\n",
    "    kurtosis = (returns.kurtosis())\n",
    "    print(f\"kurtosis: {np.round(kurtosis)}\")\n",
    "    cum_returns = np.exp(returns.cumsum())\n",
    "    drawdowns = (cum_returns.cummax() - cum_returns) / cum_returns.cummax()\n",
    "    max_drawdown = np.round(drawdowns.max(), 2)\n",
    "    print(f\"max_drawdown: {np.round(max_drawdown * 100, 2)}%\")\n",
    "    sharpe_ratio = (annualized_mean_return / annualized_volatility).round(1)\n",
    "    print(f\"sharpe_ratio: {sharpe_ratio}\")\n",
    "    downside_volatility = returns[returns < 0].std() * np.sqrt(freq)\n",
    "    sortino_ratio = (annualized_mean_return / downside_volatility).round(1)\n",
    "    print(f\"sortino_ratio: {sortino_ratio}\")\n",
    "    calmar_ratio = (annualized_mean_return / max_drawdown).round(1)\n",
    "    print(f\"calmar_ratio: {calmar_ratio}\")\n",
    "    print()\n",
    "    plt.plot(cum_returns-1, label='Cumulative Returns')\n",
    "    plt.plot(cum_returns.cummax()-1, label='Cumulative Max', linewidth=.5)\n",
    "    plt.fill_between(\n",
    "        drawdowns.index, -drawdowns, color='red', alpha=0.5, label=\"Drawdowns\"\n",
    "    )\n",
    "    # Setting x-axis major locator to each year and formatter\n",
    "    plt.gca().xaxis.set_major_locator(mdates.YearLocator())\n",
    "    plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))\n",
    "    # Setting major formatter for y-axis\n",
    "    plt.gca().yaxis.set_major_locator(ticker.MultipleLocator(0.1))\n",
    "    # Adding grid with vertical lines for each year\n",
    "    plt.grid(True, which='major', linestyle='--', color='grey')\n",
    "    # Rotate x-axis labels by 45 degrees\n",
    "    plt.xticks(rotation=45)\n",
    "    # Adjusting the legend to include all plots\n",
    "    plt.legend(loc='best')\n",
    "    # plt.yscale(\"log\")\n",
    "    plt.title(\"Cumulative Returns of Risk-Parity Portfolio\")\n",
    "    plt.savefig(\"risk_parity_returns.png\")\n",
    "    return None\n",
    "\n",
    "evaluate_performance(risk_parity_portfolio_returns)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
